<script setup>
  import chatItem from './components/chat-item.vue'
  import { computed, nextTick, onMounted, ref } from 'vue'
  import { useSafeAreaDistance } from '@/composables/useSafeAreaDistance'
  import chatActionEmoji from './components/chat-action-emoji.vue'
  import chatActionExtension from './components/chat-action-extension.vue'
  import { useChatStore, useHouseStore, useUserStore } from '@/stores'
  import { CHAT_MODE_TYPE, CHAT_MSG_TYPE } from '@/contants/message'
  import { onLoad, onUnload } from '@dcloudio/uni-app'
  import { getSessionDetailApi, getSessionIdApi, postMessageApi, putBatchReadApi } from '@/api/message'
  import { uploadFile2OSS } from '@/utils/uploadFile'
  import { formatTime } from '@/utils/time'
  const { safeArea } = useSafeAreaDistance()
  const chatStore = useChatStore()
  const userStore = useUserStore()
  const houseStore = useHouseStore()
  // 页面是否销毁
  let isDestroyed = false

  // WebSocket 地址
  const WS_URL = 'ws://47.108.58.48/chat/websocket'
  // const WS_URL = 'ws://192.168.31.239:18080/chat/websocket'
  // socket 实例
  let socketTask = null
  // 当前会话 id
  let sessionId = null
  // 最近最早消息 id
  let earlierMsgId = null

  // 页面加载时
  onLoad(async (query) => {
    connectWebSocket()
    // 创建websocket连接
    function connectWebSocket() {
      socketTask = uni.connectSocket({
        url: WS_URL + `?token=${userStore.token}`,
        complete: () => {}
      })

      socketTask.onOpen(() => {
        console.log('WebSocket连接成功！')
        // 开始心跳保活
        startHeartBeat()
      })

      socketTask.onClose(() => {
        console.log('WebSocket已关闭！')
        if (!isDestroyed) {
          // 重连逻辑
          reconnect()
        }
      })

      socketTask.onError((error) => {
        console.error('WebSocket连接失败', error)
        // 重连逻辑
        reconnect()
      })

      // 接收消息
      socketTask.onMessage((res) => {
        const data = JSON.parse(res.data)
        // ws服务器会返回自己发送的消息，自己发送的消息不用接收
        if (data.fromId === userStore.profile.userId) return
        console.log('收到来自ws服务器的消息 = ', data)
        chatList.value.push({
          ...data,
          appUserVO: chatStore.chatOther
        })
        scrollToBottom()
      })
    }
    function startHeartBeat() {
      // 设置定时器，每30秒发送一个 ping 消息
      const interval = setInterval(() => {
        socketTask.send({
          data: JSON.stringify({ ping: 'ping' }),
          success: () => {},
          fail: () => {}
        })
      }, 30000)
    }
    function reconnect() {
      if (socketTask) {
        // 等待一定时间后尝试重新连接
        setTimeout(() => {
          connectWebSocket()
        }, 1000)
      }
    }

    // 存在 sessinId
    if (query.sessionId) {
      // 从消息页过来的
      sessionId = +query.sessionId
      // 获取会话列表
      await getSessionDetail({
        id: query.msgId,
        sessionId
      })
    } else {
      // 从房源详情页过来的
      // 获取会话 id
      const resp = await getSessionIdApi({
        fromId: userStore.profile.userId,
        toId: chatStore.chatOther.id
      })
      // 如果会话不存在
      if (resp === null) {
        // 获取房源信息
        const houseContent = JSON.stringify(houseStore.currentHouse)
        // 否则 - 没聊过：发送第一次卡片消息
        onSendMsg(CHAT_MSG_TYPE.CARD, houseContent)
      } else {
        // 存在 - 聊过：获取会话列表
        await getSessionDetail(resp)
        sessionId = resp.sessionId
      }
    }
  })
  // 页面卸载时
  onUnload(async () => {
    if (socketTask) {
      socketTask.close()
    }
    isDestroyed = true
    // 把所有在 lastId 之前的消息全置为已读
    await putBatchReadApi({
      sessionId,
      toId: userStore.profile.userId
    })
    // 删除房源信息
    houseStore.removeCurrentHouse()
  })

  // 已加载图片个数
  let loadedImage = 0
  // 是否加载完成
  const isFinished = ref(false)
  // 聊天列表
  const chatList = ref([])
  // 是否是初始化
  const isInit = ref(true)
  let isFirst = true
  // 获取会话详情(聊天内容)
  async function getSessionDetail({ id, sessionId }) {
    const list = await getSessionDetailApi({ id, sessionId, first: isFirst ? 1 : 2 })
    console.log('chatDetailList', list)
    isFirst = false
    if (list.length <= 0) {
      isFinished.value = true
      return
    }

    // 翻转列表
    const reverseList = list.reduceRight((prev, item) => {
      // 卡片消息
      if (item.type === CHAT_MSG_TYPE.CARD) {
        item.content = JSON.parse(item.content)
      }
      prev.push(item)
      return prev
    }, [])
    // 合并数组
    chatList.value = [...reverseList, ...chatList.value]
    // 更新较早消息 id
    earlierMsgId = list[list.length - 1].id
  }
  // 是否在加载中
  const isLoading = ref(false)

  // 是否是上拉
  let isPullUp = false

  // 触顶加载更多
  const onReachTop = async () => {
    isPullUp = true
    if (isFinished.value)
      return uni.showToast({
        title: '没有更多聊天记录了',
        icon: 'none'
      })

    if (!isLoading.value) {
      isLoading.value = true
      await getSessionDetail({
        id: earlierMsgId,
        sessionId
      })
      isLoading.value = false
    }
  }

  // scroll view 滚动到底部
  function scrollToBottom() {
    scrollIntoView.value = ''
    nextTick(() => {
      scrollIntoView.value = 'bottom'
    })
  }

  // 聊天操作区顶底部定位值
  const ACTION_BOTTOM = ref(safeArea.value.bottom)

  const scrollIntoView = ref(null)
  // 监听键盘高度变化
  uni.onKeyboardHeightChange((res) => {
    ACTION_BOTTOM.value = res.height || safeArea.value.bottom
    if (res.height > 0) {
      // scroll-view 滚动到底部
      scrollToBottom()
    } else {
      scrollIntoView.value = null
    }
  })

  // 聊天列表区域底部定位值
  const CHAT_AREA_BOTTOM = computed(() => {
    return ACTION_BOTTOM.value + uni.upx2px(120)
  })

  // 点击页面
  const onClickPage = () => {
    onBlur()
    visible.value = false
    ACTION_BOTTOM.value = safeArea.value.bottom
    scrollIntoView.value = null
  }

  // 输入框的内容
  const msgText = ref('')

  // 弹出层是否可见
  const visible = ref(false)

  // 输入框是否聚焦
  const isFocus = ref(false)

  // 打开弹出层
  const onOpen = (mode) => {
    chatStore.setCurrentChatMode(mode)
    if (![CHAT_MODE_TYPE.EMOJI, CHAT_MODE_TYPE.EXTENSION].includes(mode)) return
    visible.value = true
    ACTION_BOTTOM.value = uni.upx2px(490)
    scrollToBottom()
    // 如果是键盘模式
    if (mode === CHAT_MODE_TYPE.KEYBOARD) {
      // 关闭弹出层
      visible.value = false
      // 显示键盘(让输入框聚焦即可)
      isFocus.value = true
    }
  }

  // 失焦
  const onBlur = () => {
    isFocus.value = false
    scrollIntoView.value = null
  }
  // 聚焦
  const onFocus = async () => {
    isFocus.value = true
    visible.value = false
  }

  // 切换左侧图标
  const changeChatMode = (mode) => {
    chatStore.setCurrentChatMode(
      chatStore.currentChatMode !== CHAT_MODE_TYPE.VOICE ? CHAT_MODE_TYPE.VOICE : CHAT_MODE_TYPE.KEYBOARD
    )
    if (mode === CHAT_MODE_TYPE.VOICE) {
      isFocus.value = false
      setTimeout(() => {
        visible.value = false
        ACTION_BOTTOM.value = safeArea.value.bottom
        scrollIntoView.value = null
      }, 50)
    } else {
      isFocus.value = true
    }
  }

  // 选择图标
  const onSelect = (item) => {
    msgText.value += item.remark
  }

  // 删除图标
  const onEmojiBack = () => {
    const { value } = msgText
    if (value) {
      let number = 1
      if (value.length > 1) {
        number =
          value
            .substring(value.length - 2, value.length)
            .search(/(\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f])|(\ud83d[\ude80-\udeff])/i) == -1
            ? 1
            : 2
      }
      msgText.value = value.substring(0, value.length - number)
    }
  }

  // 发送消息
  const onSendMsg = async (type, content) => {
    // if (!socketTask) return
    // 发送给服务器的数据
    let data = {
      type,
      fromId: userStore.profile.userId,
      toId: chatStore.chatOther.id,
      sessionId,
      status: 1,
      createTime: formatTime(new Date(), 'YYYY-MM-DD HH:mm:ss')
    }
    switch (type) {
      // 文本/表情
      case CHAT_MSG_TYPE.TEXT:
      // 图片
      case CHAT_MSG_TYPE.IMAGE:
        data.content = content
        break
      // 语音
      case CHAT_MSG_TYPE.VOICE:
        const filePath = await uploadFile2OSS({
          tempFilePath: content,
          fileType: 'audio'
        })
        data.content = filePath
        data.time = Math.round(chatStore.recorderTime)
        break
      // 卡片
      case CHAT_MSG_TYPE.CARD:
        data.content = content
        const msg = await postMessageApi(data)
        // 记录 sessionId
        sessionId = msg.sessionId
        // 记录较早的消息id
        earlierMsgId = msg.id
        // 添加消息时聊天列表
        chatList.value.push({
          ...msg,
          content: houseStore.currentHouse,
          appUserVO: userStore.profile
        })
        return
    }
    // 发送消息至ws服务器
    console.log('JSON.stringify(data) = ', JSON.stringify(data))
    socketTask.send({
      data: JSON.stringify(data),
      success: () => {
        console.log('ws消息发送成功')
        // 如果是文本消息
        if (type === CHAT_MSG_TYPE.TEXT) {
          if (chatStore.currentChatMode === CHAT_MODE_TYPE.KEYBOARD) {
            isFocus.value = true
          }
          msgText.value = ''
        }
        // 添加至聊天列表
        chatList.value.push({
          ...data,
          appUserVO: userStore.profile
        })
        // 滚动到底部
        setTimeout(() => {
          scrollToBottom()
        }, 50)
      },
      fail: (err) => {
        console.log('ws消息发送失败', err)
      }
    })
    // await postMessageApi(data)
  }

  // 录音
  const isRecording = ref(false)
  // 触摸开始的纵向距离
  const startTouchY = ref(0)
  // 是否取消
  const isCancel = ref(false)
  // 1. 触摸开始
  const onStart = (e) => {
    isCancel.value = false
    isRecording.value = true
    startTouchY.value = e.touches[0].screenY
    // 开始录音
    chatStore.recorder.start({
      duration: 60000,
      format: 'mp3'
    })
  }
  // 2. 触摸移动
  const onMove = ({ touches }) => {
    // 移动的纵向距离
    const moveY = touches[0].screenY
    // 判断是否取消
    isCancel.value = startTouchY.value - moveY > 50
  }
  // 3. 触摸结束
  const onEnd = () => {
    isRecording.value = false
    // 停止录音
    chatStore.recorder.stop()
  }

  // 4. 录音打断(取消)
  const onCancel = () => {
    isCancel.value = true
    isRecording.value = false
  }
  onMounted(() => {
    // 注册发送消息事件
    chatStore.registerSendVoiceEvent((tempFilePath) => {
      if (!isCancel.value) {
        // 发送语音
        onSendMsg(CHAT_MSG_TYPE.VOICE, tempFilePath)
      }
    })
  })

  // 图片消息加载结束
  const onLoaded = () => {
    // 不是第一次获取聊天列表(发送图片消息时)
    if (!isInit.value) {
      // 滚动到底部
      scrollToBottom()
    } else {
      if (!isPullUp) {
        loadedImage++
        // 图片总数量
        const totalImageCount = chatList.value.filter((item) => item.type === CHAT_MSG_TYPE.IMAGE).length
        if (totalImageCount === loadedImage) {
          setTimeout(scrollToBottom, 300)
          isInit.value = false
        }
      }
    }
  }
</script>

<template>
  <!-- 自定义头部导航栏 -->
  <bit-nav-bar :title="chatStore.chatOther.nickName" />
  <!-- 聊天列表 -->
  <scroll-view
    :scroll-into-view="scrollIntoView"
    scroll-y
    class="chat-list"
    :style="{ bottom: `${CHAT_AREA_BOTTOM}px` }"
    :scroll-with-animation="true"
    @click="onClickPage"
    @scrolltoupper="onReachTop"
  >
    <chat-item
      v-for="(item, index) in chatList"
      :key="item.id"
      :item="item"
      :index="index"
      @loaded="onLoaded"
    />
    <view
      id="bottom"
      style="height: 0.5rpx; width: 100vw; background: transparent"
    ></view>
  </scroll-view>
  <!-- 底部发送区 -->
  <view
    class="chat-action"
    :style="{ bottom: `${ACTION_BOTTOM}px` }"
  >
    <view class="left">
      <!-- 语音图标 -->
      <image
        v-if="chatStore.currentChatMode !== CHAT_MODE_TYPE.VOICE"
        src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/laba.png"
        mode="widthFix"
        class="icon icon-laba"
        @click="changeChatMode(CHAT_MODE_TYPE.VOICE)"
      />
      <!-- 键盘图标 -->
      <image
        v-else
        src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/jianpan.png"
        mode="widthFix"
        class="icon icon-jianpan"
        @click="changeChatMode(CHAT_MODE_TYPE.KEYBOARD)"
      />
    </view>
    <view class="middle">
      <!-- 按住说话 -->
      <view
        v-if="chatStore.currentChatMode === CHAT_MODE_TYPE.VOICE"
        class="btn_longpress"
        @touchstart="onStart"
        @touchmove="onMove"
        @touchend="onEnd"
        @touchcancel="onCancel"
        :class="{ isRecording }"
        >{{ isRecording ? '松开 结束' : '按住 说话' }}</view
      >
      <!-- 输入框 -->
      <input
        v-else
        v-model.trim="msgText"
        :adjust-position="false"
        confirm-type="send"
        class="input"
        :focus="isFocus"
        placeholder="对Ta发送消息"
        @focus="onFocus"
        @blur="onBlur"
      />
    </view>
    <view class="right">
      <button
        v-if="msgText"
        type="primary"
        class="btn_send"
        @click="onSendMsg(CHAT_MSG_TYPE.TEXT, msgText)"
      >
        发送
      </button>
      <block v-else>
        <image
          v-if="chatStore.currentChatMode !== CHAT_MODE_TYPE.EMOJI"
          src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/biaoqing.png"
          mode="widthFix"
          class="icon icon-biaoqing"
          @click="onOpen(CHAT_MODE_TYPE.EMOJI)"
        />
        <image
          v-else
          src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/jianpan.png"
          mode="widthFix"
          class="icon icon-jianpan"
          @click="onOpen(CHAT_MODE_TYPE.KEYBOARD)"
        />
        <image
          src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/gengduo.png"
          mode="widthFix"
          class="icon icon-gengduo"
          @click="onOpen(CHAT_MODE_TYPE.EXTENSION)"
        />
      </block>
    </view>
  </view>
  <!-- 自定义弹出层 -->
  <view
    class="popup"
    v-if="visible"
  >
    <chatActionEmoji
      v-if="chatStore.currentChatMode === CHAT_MODE_TYPE.EMOJI"
      :hasContent="!!msgText"
      @select="onSelect"
      @del="onEmojiBack"
    />
    <chatActionExtension
      @success="onSendMsg(CHAT_MSG_TYPE.IMAGE, $event)"
      v-if="chatStore.currentChatMode === CHAT_MODE_TYPE.EXTENSION"
    />
  </view>
  <!-- 语音模态框 -->
  <view
    class="voice-modal"
    v-if="isRecording"
  >
    <view class="voice-modal-content">
      <view class="voice-modal-icon">
        <image
          class="recording-gif"
          src="https://bitejiaoyan.oss-cn-chengdu.aliyuncs.com/bitehouse/web/message/recording.gif"
          mode="widthFix"
        />
      </view>
      <view class="voice-modal-text">{{ isCancel ? '松开手指' : '手指上滑' }}, 取消发送</view>
    </view>
  </view>
</template>

<style lang="scss">
  @import '@/styles/variable.scss';
  @import '@/styles/mixins.scss';
  // 聊天列表
  .chat-list {
    box-sizing: border-box;
    position: fixed;
    left: 0;
    right: 0;
    top: 176rpx;
    @include bottom-safeArea(120);
    padding: 30rpx;
    background-color: #f5f5f5;
    // 消息时间
    .time {
      text-align: center;
      color: #999;
      font-size: 26rpx;
      padding: 15rpx 0;
    }
    // 通用消息样式
    .msg {
      display: flex;
      margin: 15rpx 0;
      .avatar {
        width: 80rpx;
        height: 80rpx;
        border-radius: 100%;
      }
      &.myself {
        justify-content: flex-end;
        .avatar {
          margin-left: 20rpx;
        }
      }
      &.other {
        justify-content: flex-start;
        .avatar {
          margin-right: 20rpx;
        }
      }
    }

    // 取消
    .cancel {
      position: fixed;
      left: 0;
      right: 0;
      bottom: 500rpx;
      width: 200rpx;
      height: 100rpx;
      margin: auto;
      line-height: 100rpx;
      text-align: center;
      color: #fff;
      font-size: var(--font-size-md);
      border-radius: 48rpx;
      background-color: rgba(0, 0, 0, 0.4);
    }
  }
  // 底部区域
  .chat-action {
    box-sizing: border-box;
    z-index: 3;
    position: fixed;
    left: 0;
    @include bottom-safeArea;
    display: flex;
    align-items: center;
    width: 100vw;
    height: 120rpx;
    padding: 0 20rpx;
    background-color: #fff;
    .icon {
      width: 54rpx;
      height: 54rpx;
    }
    .left {
      padding-right: 30rpx;
    }
    .middle {
      display: flex;
      align-items: center;
      width: 456rpx;
      height: 80rpx;
      flex: 1;
      .input,
      .btn_longpress {
        box-sizing: border-box;
        flex: 1;
        width: 100%;
        height: 100%;
        line-height: 3;
        padding-left: 20rpx;
        border-radius: 16rpx;
        background-color: #f2f2f4;
        font-size: var(--font-size-sm);
      }
      .btn_longpress {
        text-align: center;
        color: var(--text-color-light);
        &.isRecording {
          background-color: #dedede;
        }
      }
    }
    .right {
      display: flex;
      .icon-biaoqing,
      .icon-jianpan {
        margin: 0 24rpx;
      }
      .btn_send {
        width: 132rpx;
        height: 68rpx;
        margin-left: 24rpx;
        line-height: 68rpx;
        background-color: #0fa1fe;
        font-size: 26rpx;
        color: #fff;
      }
    }
  }
  // 弹出层
  .popup {
    box-sizing: border-box;
    z-index: 4;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100vw;
    height: 490rpx;
    padding-bottom: env(safe-area-inset-bottom);
    padding-bottom: constant(safe-area-inset-bottom);
    background-color: #d8d8d8;
  }

  .voice-modal {
    position: fixed;
    left: 0;
    right: 0;
    top: 176rpx;
    bottom: 120rpx;
    display: flex;
    justify-content: center;
    align-items: center;
    &-content {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      width: 360rpx;
      height: 360rpx;
      border-radius: 36rpx;
      background-color: rgba(0, 0, 0, 0.5);
      .recording-gif {
        width: 150rpx;
        height: 150rpx;
      }
      .voice-modal-text {
        margin-top: 20rpx;
        font-size: var(--font-size-md);
        color: #fff;
      }
    }
  }
</style>
